/**
 * License would add here
 */

#ifndef UTILS_JS_NUMBER_H
#define UTILS_JS_NUMBER_H

#include <csignal>
#include <cstring>
#include <js_native_api.h>

#include <node_api.h>

#include "js_value_base.h"
#include "js_value_ref.h"
#include "macros.h"

class JSValueRef;

#define IS_NUMBER(type) VALUE_TYPE_CHECK(IsNumber(), "number", (type))

typedef int32_t int32;
typedef uint32_t uint32;
typedef int64_t int64;

class JSNumber : public JSValue {
public:
#define VALUE_CONSTRUCTOR(type)                  \
    JSNumber(napi_env env, type value)           \
        : JSValue(env)                           \
    {                                            \
        napi_create_##type(env, value, &value_); \
        UpdateType();                            \
    }

    VALUE_CONSTRUCTOR(int32)
    VALUE_CONSTRUCTOR(uint32)
    VALUE_CONSTRUCTOR(int64)
    VALUE_CONSTRUCTOR(double)

#undef VALUE_CONSTRUCTOR

    JSNumber(napi_env env)
        : JSValue(env)
    {
        napi_create_int32(env, 0, &value_);
        UpdateType();
    }

    JSNumber(napi_env env, napi_value value)
        : JSValue(env, value)
    {
        IS_NUMBER(type_);
    }
    JSNumber(const JSValue& value)
        : JSValue(value)
    {
        IS_NUMBER(type_);
    };

    template <typename T>
    T GetValue() const;

    template <typename T>
    inline operator T() const { return GetValue<T>(); };

#define DEFINE_VALUE_METHOD(type)                 \
    inline JSNumber operator=(const type& value)  \
    {                                             \
        napi_create_##type(env_, value, &value_); \
        return *this;                             \
    }

    DEFINE_VALUE_METHOD(int32)
    DEFINE_VALUE_METHOD(uint32)
    DEFINE_VALUE_METHOD(int64)
    DEFINE_VALUE_METHOD(double)

#undef DEFINE_VALUE_METHOD

#define DEFINE_CALC_METHOD(type)                   \
    inline JSNumber operator+(const type& value);  \
    inline JSNumber operator+=(const type& value); \
    inline JSNumber operator-(const type& value);  \
    inline JSNumber operator-=(const type& value)

    DEFINE_CALC_METHOD(int32);
    DEFINE_CALC_METHOD(uint32);
    DEFINE_CALC_METHOD(int64);
    DEFINE_CALC_METHOD(double);
    DEFINE_CALC_METHOD(JSNumber);
    DEFINE_CALC_METHOD(napi_value);

#undef DEFINE_CALC_METHOD

#define DEFINE_BINARY_METHOD(type)                  \
    inline JSNumber operator<<(const type& value);  \
    inline JSNumber operator>>(const type& value);  \
    inline JSNumber operator<<=(const type& value); \
    inline JSNumber operator>>=(const type& value)

    DEFINE_BINARY_METHOD(int32_t);
    DEFINE_BINARY_METHOD(uint32_t);
    DEFINE_BINARY_METHOD(int64_t);
    DEFINE_BINARY_METHOD(JSNumber);
    DEFINE_BINARY_METHOD(napi_value);

#undef DEFINE_BINARY_METHOD

#define DEFINE_LOGIC_METHOD(type)              \
    inline bool operator>(const type& value);  \
    inline bool operator>=(const type& value); \
    inline bool operator<(const type& value);  \
    inline bool operator<=(const type& value)

    DEFINE_LOGIC_METHOD(int32);
    DEFINE_LOGIC_METHOD(uint32);
    DEFINE_LOGIC_METHOD(int64);
    DEFINE_LOGIC_METHOD(double);
    DEFINE_LOGIC_METHOD(JSNumber);
    DEFINE_LOGIC_METHOD(napi_value);

#undef DEFINE_LOGIC_METHOD

    inline bool operator==(const int32& value);
    inline bool operator==(const uint32& value);
    inline bool operator==(const int64& value);
    inline bool operator==(const double& value);

    JSBoolean IsFinite();
    JSBoolean IsInteger();
    JSBoolean IsNaN();
    JSBoolean IsSafeInteger();

private:
    static void InitNumberUtils(napi_env env);
    thread_local static JSValueRef* g_numberIsFinite;
    thread_local static JSValueRef* g_numberIsInteger;
    thread_local static JSValueRef* g_numberIsNaN;
    thread_local static JSValueRef* g_numberIsSafeInteger;
};

#define EXPAND_VALUE_METHOD(type)                     \
    template <>                                       \
    inline type JSNumber::GetValue<type>() const      \
    {                                                 \
        type result = 0;                              \
        napi_get_value_##type(env_, value_, &result); \
        return result;                                \
    }

EXPAND_VALUE_METHOD(int32)
EXPAND_VALUE_METHOD(uint32)
EXPAND_VALUE_METHOD(int64)
EXPAND_VALUE_METHOD(double)

#undef EXPAND_VALUE_METHOD

#define EXPAND_CALC_METHOD(type, out, V)                       \
    inline JSNumber JSNumber::operator+(const type& value)     \
    {                                                          \
        return JSNumber(env_, GetValue<out>() + V);            \
    }                                                          \
    inline JSNumber JSNumber::operator+=(const type& value)    \
    {                                                          \
        napi_create_##out(env_, GetValue<out>() + V, &value_); \
        return *this;                                          \
    }                                                          \
    inline JSNumber JSNumber::operator-(const type& value)     \
    {                                                          \
        return JSNumber(env_, GetValue<out>() - V);            \
    }                                                          \
    inline JSNumber JSNumber::operator-=(const type& value)    \
    {                                                          \
        napi_create_##out(env_, GetValue<out>() - V, &value_); \
        return *this;                                          \
    }

#define VB value
EXPAND_CALC_METHOD(int32, int32, VB)
EXPAND_CALC_METHOD(uint32, uint32, VB)
EXPAND_CALC_METHOD(int64, int64, VB)
EXPAND_CALC_METHOD(double, double, VB)
#define VJ value.GetValue<double>()
EXPAND_CALC_METHOD(JSNumber, double, VJ)
#undef VJ
#define VN JSNumber(env_, value).GetValue<double>()
EXPAND_CALC_METHOD(napi_value, double, VN)
#undef VN

#undef EXPAND_CALC_METHOD

#define EXPAND_BINARY_METHOD(type, V)                        \
    inline JSNumber JSNumber::operator<<(const type& value)  \
    {                                                        \
        return JSNumber(env_, GetValue<int64_t>() << V);     \
    }                                                        \
    inline JSNumber JSNumber::operator>>(const type& value)  \
    {                                                        \
        return JSNumber(env_, GetValue<int64_t>() >> V);     \
    }                                                        \
    inline JSNumber JSNumber::operator<<=(const type& value) \
    {                                                        \
        value_ = JSNumber(env_, GetValue<int64_t>() << V);   \
        return *this;                                        \
    }                                                        \
    inline JSNumber JSNumber::operator>>=(const type& value) \
    {                                                        \
        value_ = JSNumber(env_, GetValue<int64_t>() >> V);   \
        return *this;                                        \
    }

#define VB value
EXPAND_BINARY_METHOD(int32, VB)
EXPAND_BINARY_METHOD(uint32, VB)
EXPAND_BINARY_METHOD(int64, VB)
#undef VB
#define VJ value.GetValue<int64_t>()
EXPAND_BINARY_METHOD(JSNumber, VJ)
#undef VJ
#define VN JSNumber(env_, value).GetValue<int64_t>()
EXPAND_BINARY_METHOD(napi_value, VN)
#undef VN

#undef EXPAND_BINARY_METHOD

#define EXPAND_BASE_LOGIC_METHOD(type, out, V)          \
    inline bool JSNumber::operator>(const type& value)  \
    {                                                   \
        return GetValue<out>() > V;                     \
    }                                                   \
    inline bool JSNumber::operator>=(const type& value) \
    {                                                   \
        return GetValue<out>() >= V;                    \
    }                                                   \
    inline bool JSNumber::operator<(const type& value)  \
    {                                                   \
        return GetValue<out>() < V;                     \
    }                                                   \
    inline bool JSNumber::operator<=(const type& value) \
    {                                                   \
        return GetValue<out>() <= V;                    \
    }

#define VB value
EXPAND_BASE_LOGIC_METHOD(int32, int32, VB)
EXPAND_BASE_LOGIC_METHOD(uint32, uint32, VB)
EXPAND_BASE_LOGIC_METHOD(int64, int64, VB)
EXPAND_BASE_LOGIC_METHOD(double, double, VB)
#undef VB

#define VJ value.GetValue<double>()
EXPAND_BASE_LOGIC_METHOD(JSNumber, double, VJ)
#undef VJ

#define VN JSNumber(env_, value).GetValue<double>()
EXPAND_BASE_LOGIC_METHOD(napi_value, double, VN)
#undef VN

#undef EXPAND_BASE_LOGIC_METHOD

#define EXPAND_EQUALS_METHOD(type)                      \
    inline bool JSNumber::operator==(const type& value) \
    {                                                   \
        return GetValue<type>() == value;               \
    }

EXPAND_EQUALS_METHOD(int32)
EXPAND_EQUALS_METHOD(uint32)
EXPAND_EQUALS_METHOD(int64)
EXPAND_EQUALS_METHOD(double)

#undef EXPAND_EQUALS_METHOD

inline JSNumber JSValue::ToNumber() const
{
    JSValue object(env_);
    napi_coerce_to_number(env_, value_, &object);
    object.UpdateType();
    return object.As<JSNumber>();
}

#undef IS_NUMBER
#endif /* UTILS_JS_NUMBER_H */
